package com.macro.mall.common.msg;



import com.macro.mall.common.enums.BizCode;
import com.macro.mall.common.exception.BaseException;
import com.macro.mall.common.exception.ExceptionCode;
import com.macro.mall.common.exception.TraceContext;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;


@Getter
@Setter
@Accessors(chain = true)
public class Result<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final int defaultCode = 9999;

    /**
     * 请求是否成功
     */

    private boolean success;

    /**
     * 返回code码
     */
    private int status;

    /**
     * 返回消息
     */
    private String message;

    /**
     * traceId
     */
    private String traceId;

    /**
     * 响应参数实体
     */
    private T data;

    /**
     * 扩展内容
     */
    private Map<String, Object> extra;

    private Result() {
    }

    /**
     * 添加扩展内容
     *
     * @param key   字段名
     * @param value 值
     * @return
     */
    public Result<T> addExtra(String key, Object value) {
        this.extra = this.extra == null ? new HashMap<>(16) : this.extra;
        this.extra.put(key, value);
        return this;
    }

    /**
     * 获取返回结果
     *
     * <p>
     * 1.会校验当前请求是否成功，如果不成功则直接抛出异常
     * </p>
     * <p>
     * 2.如果请求成功，但并未返回结果的情况下默认返回空
     * </p>
     * <p>
     * 3.与optionalData不同，没有optionalData().orElse(Lists.newArrayList())的默认值设置选项
     * </p>
     *
     * @return T
     */
    public T data() throws BaseException {
        if (!success) throw new BaseException(status, message);
        return this.data;
    }

    /**
     * 获取一个Optional包装的返回结果
     * <p>
     * 会校验当前调用是否成功，并且返回一个Optional对象，取代if操作
     * </p>
     * 例(1)
     * <pre>
     * PageVo<BusinessVO> pageVo = client.method(dto).optionalData()
     * .filter(pageDbVo -> Collections.isNotEmpty(pageDbVo.getRecords()))
     * .map(pageDbVo -> PageVo.reBuilder(
     *  pageDbVo, holder.convert(pageDbVo.getRecords(), BusinessVO.class))
     * );
     *
     * </pre>
     * 例(2)
     * <pre>
     * client.method(dto)
     * .optionalData()
     * .filter(CollectionUtils::isNotEmpty)
     * .ifPresent(it -> query.in(Order::getId, it));
     * </pre>
     *
     * @return Optional<T>
     */
    public Optional<T> optionalData() throws BaseException {
        if (!success) throw new BaseException(status, message);
        return Optional.ofNullable(this.data);
    }

    /**
     * 请求成功后做某些事情
     * <p>
     * 如果请求不成功也不会抛出异常，如果需要抛出异常请查看{@link #failThrow}
     * </p>
     *
     * @param consumer 消费者
     * @return Result<T>
     */
    public Result<T> ok(Consumer<T> consumer) {
        if (success) consumer.accept(this.data);
        return this;
    }

    /**
     * 请求失败之后做某些事情
     * <p>
     * 如果失败需要抛出异常请查看{@link #failThrow}
     * </p>
     *
     * @param consumer 消费者
     * @return Result<T>
     */
    public Result<T> fail(BiConsumer<Integer, String> consumer) {
        if (!success) consumer.accept(this.status, this.message);
        return this;
    }


    /**
     * <p>
     * 请求失败则抛出异常
     * </p>
     * 例如
     * <pre>
     * client.method(A,B).failThrow();
     * </pre>
     *
     * @return Boolean
     */
    public Boolean failThrow() throws BaseException {
        if (!success) throw new BaseException(status, message);
        return Boolean.TRUE;
    }



    public static <T> Result<T> failure(BizCode bizCode) {
        return failure(bizCode.getCode(), bizCode.getMessage());
    }


    public static <T> Result<T> failure(int code, String msg) {
        return init(code, msg, null);
    }

    public static <T> Result<T> failure(String msg) {
        return init(400, msg, null);
    }
    public static <T> Result<T> success() {
        return success(null);
    }

    public static <T> Result<T> success(T data) {
        return init(ExceptionCode.OK.getCode(), ExceptionCode.OK.getMsg(), data);
    }

    public static <T> Result<T> init(int code, String msg, T data) {
        return new Result<T>().setSuccess(ExceptionCode.OK.getCode() == code).setStatus(code).setTraceId(TraceContext.traceId()).setMessage(msg).setData(data);
    }
}
